package org.xmppbot.net.nio;

import org.apache.mina.core.buffer.IoBuffer;
import org.apache.mina.core.filterchain.IoFilterChain;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.compression.CompressionFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import org.xmppbot.Connection;
import org.xmppbot.session.LocalSession;
import org.xmppbot.session.Session;

/**
 * При помощи данного класса бот-сервер будет отправлять ответные станзы
 * @author stas
 *
 */
class BotNIOConnection implements Connection{
    /**
     * The utf-8 charset for decoding and encoding XMPP packet streams.
     */
    public static final String CHARSET = "UTF-8";
    
    private LocalSession _session;
    private IoSession _ioSession;
    private String _language = null;

    /**
     * Flag that specifies if the connection should be considered closed. Closing a NIO connection
     * is an asynch operation so instead of waiting for the connection to be actually closed just
     * keep this flag to avoid using the connection between #close was used and the socket is actually
     * closed.
     */
    private boolean closed;
    /**
     * Compression policy currently in use for this connection.
     */
    private CompressionPolicy compressionPolicy = CompressionPolicy.disabled;
    /**
     * TLS policy currently in use for this connection.
     */
    private TLSPolicy _tlsPolicy = TLSPolicy.optional;

    private Logger _logger = LoggerFactory.getLogger(BotNIOConnection.class);

    public BotNIOConnection(IoSession ioSession) {
    	_ioSession = ioSession;
    }
    
    @Override
	public void close() {
    	_ioSession.close(false); //false to close this session after all queued write requests are flushed (i.e. #closeOnFlush())
	}
	@Override
	public void init(LocalSession owner) {
		_session = owner;
	}
	@Override
	public boolean validate() {
        if (isClosed()) {
            return false;
        }
        deliver(" ");
        return !isClosed();
	}
	@Override
	public boolean isClosed() {
        if (_session == null) {
            return closed;
        }
        return _session.getStatus() == Session.STATUS_CLOSED;
	}

	@Override
	public void deliver(String stanza) {
		deliver(stanza, true);
	}
    
	private void deliver(String stanza, boolean asynchronous){
		if (!isClosed()){
            IoBuffer buffer = IoBuffer.allocate(stanza.length());
            buffer.setAutoExpand(true);
            
            boolean errorDelivering = false;
            try{
            	buffer.put(stanza.getBytes(CHARSET));
            	buffer.flip();
            	if (asynchronous){
            		_ioSession.write(buffer.toString());
            	}else{
                    // Send stanza and wait for ACK (using a 2 seconds default timeout)
                    boolean ok =
                            _ioSession.write(buffer.toString()).await(2000);
                    if (!ok) {
                        _logger.warn("No ACK was received when sending stanza to: " + this.toString());
                    }
            		
            	}
            }catch (Exception e){
            	errorDelivering = true;
            	_logger.error("Ошибка при отправке String станзы!", e);
            }

            if (errorDelivering && asynchronous) {
                close();
            }
			
		}
	}
    
	@Override
	public void addCompression() {
        IoFilterChain chain = _ioSession.getFilterChain();
        String baseFilter = "org.apache.mina.common.ExecutorThreadModel";
        if (chain.contains("tls")) {
            baseFilter = "tls";
        }
        chain.addAfter(baseFilter, "compression", new CompressionFilter(true, false, CompressionFilter.COMPRESSION_MAX));
	}

	@Override
	public CompressionPolicy getCompressionPolicy() {
        return compressionPolicy;
	}

	@Override
	public String getLanguage() {
		return _language;
	}

	@Override
	public TLSPolicy getTlsPolicy() {
		return _tlsPolicy;
	}

	@Override
	public boolean isCompressed() {
		return _ioSession.getFilterChain().contains("compression");
	}

	@Override
	public boolean isSecure() {
		return _ioSession.getFilterChain().contains("tls");
	}

	@Override
	public void startCompression() {
        CompressionFilter ioFilter = (CompressionFilter) _ioSession.getFilterChain().get("compression");
        ioFilter.setCompressOutbound(true);
	}
}
